﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Newtonsoft.Json;

namespace ShieldTunnel2
{
	/// <summary>
	/// 风险评估函数
	/// </summary>
	public static class RiskEval
	{
		/// <summary>
		/// 计算专家权重系数
		/// </summary>
		/// <param name="experts">专家列表</param>
		/// <returns>专家权重</returns>
		public static IList<double> GetExpertWeight(IList<Expert> experts)
		{
			var exportScore = experts
				.Select(e => e.ProfessionalScores.Sum())
				.ToArray();
			var totalScore = exportScore.Sum();
			var weights = exportScore
				.Select(es => ((double) es) / totalScore)
				.ToArray();
			for (var i = 0; i < experts.Count; i++)
			{
				experts[i].Weight = weights[i];
			}
			Debug.Assert(Math.Abs(weights.Sum() - 1d) < 1e-6);
			return weights;
		}

		/// <summary>
		/// 根据"1-9标度法"和专家打分（1-9），按80%累计频率计算分值
		/// </summary>
		/// <param name="scores">专家打分</param>
		/// <returns>分值</returns>
		public static double GetScoreBy19ScaleMethod(IList<int> scores)
		{
			Debug.Assert(scores.All(s => 1 <= s && s <= 9));
			var freqs = Enumerable.Range(1, 9)
				.Select(i => (Score: i, Freq: (double) scores.Count(s => s == i) / scores.Count));
			var orderedFreqs = freqs.OrderByDescending(f => f.Freq * 100d + f.Score);

			var totalScore = 0d;
			var totalFreq = 0d;
			foreach (var freq in orderedFreqs)
			{
				totalScore += freq.Score * freq.Freq;

				totalFreq += freq.Freq;
				if (totalFreq >= 0.8)
					break;
			}

			return totalScore;
		}

		/// <summary>
		/// 通过比较相对重要性程度(重要度)得到各元素的权重
		/// </summary>
		/// <param name="scores">重要度分值</param>
		/// <returns>权重</returns>
		public static IList<double> GetWeightsByImportanceComparison(IList<double> scores)
		{
			Debug.Assert(scores != null && scores.Count > 0 && scores.All(s => s >= 0));
			var n = scores.Count;
			if (n == 1)
			{
				return new[] {1d};
			}
			else if (n == 2)
			{
				return scores.Select(s => s / scores.Sum()).ToArray();
			}

			Debug.Assert(n >= 3);
			var df = scores.Max() - scores.Min();
			var spliters = new[] {-0.1 * df, 0, 0.25 * df, 0.5 * df, 0.75 * df, df};
			var levels = new[] {1d, 3d, 5d, 7d, 9d};

			//
			// 计算判断矩阵A
			//
			var a = new double[n, n];
			for (var i = 0; i < n; i++)
			{
				for (var j = 0; j < n; j++)
				{
					var ds = scores[i] - scores[j];
					var level = MatchLevel(Math.Abs(ds), spliters, levels,
						(v, l, u) => l < v && v <= u);
					Debug.Assert(1d <= level && level <= 9d);
					a[i, j] = ds >= 0 ? level : (1d / level);
				}
			}

			//
			// 调整判断矩阵A，以符合一致性检验
			//
			var ap = GetAdjustedComparisonMatrix(a);

			//
			// 计算相对权重W
			//
			var w = GetWeightVector(ap);
			Debug.Assert(Math.Abs(w.Sum() - 1d) < 1e-6);
			return w;
		}

		/// <summary>
		/// 调整判断矩阵A，以符合一致性检验
		/// </summary>
		/// <param name="a">原判断矩阵</param>
		/// <returns>调整后的判断矩阵</returns>
		public static double[,] GetAdjustedComparisonMatrix(double[,] a)
		{
			Debug.Assert(a.GetLength(0) == a.GetLength(1));
			var n = a.GetLength(0);
			Debug.Assert(n > 2);

			var ap = new double[n, n];
			for (var i = 0; i < n; i++)
			{
				for (var j = 0; j < n; j++)
				{
					const double eps = 1e-6;
					Debug.Assert(a[i, j] > 0 && Math.Abs(a[i, j] * a[j, i] - 1d) < eps);
					ap[i, j] = a[i, j];
				}
			}

			var trialCount = 0;
			while (true)
			{
				trialCount++;
				Debug.Assert(trialCount < 10000);

				var cr = GetConsistenceRatio(ap);
				if (cr <= 0.1)
					break;

				var c = GetInduceMatrix(ap);

				int k = 0, l = 0;
				var dmax = 0d;
				for (var i = 0; i < n; i++)
				{
					for (var j = 0; j < n; j++)
					{
						if (Math.Abs(c[i, j] - 1d) > dmax)
						{
							dmax = Math.Abs(c[i, j] - 1d);

							k = i;
							l = j;
						}
					}
				}

				Debug.Assert(Math.Abs(c[k, l] - 1d) >= 0);
				if (c[k, l] > 1)
				{
					if (ap[k, l] > 1d)
					{
						ap[k, l] = ap[k, l] - 1;
					}
					else
					{
						ap[k, l] = 1d / (1d / ap[k, l] + 1d);
					}
				}
				else
				{
					if (ap[k, l] > 1d)
					{
						ap[k, l] = ap[k, l] + 1;
					}
					else
					{
						ap[k, l] = 1d / (1d / ap[k, l] - 1d);
					}
				}
				ap[l, k] = 1d / ap[k, l];
			}
			return ap;
		}

		/// <summary>
		/// 计算判断矩阵A的权重向量W
		/// </summary>
		/// <param name="a"></param>
		/// <returns>权重向量W</returns>
		public static double[] GetWeightVector(double[,] a)
		{
			Debug.Assert(a.GetLength(0) == a.GetLength(1));
			var n = a.GetLength(0);

			// 判断矩阵A各行元素之积
			var b = Enumerable.Repeat(1d, n).ToArray();
			for (var i = 0; i < n; i++)
			{
				for (var j = 0; j < n; j++)
				{
					b[i] *= a[i, j];
				}
			}

			// bi的n次方根P
			var p = b.Select(x => Math.Pow(x, 1d / n)).ToArray();

			// 将P正规化，求权重向量W
			var ps = p.Sum();
			var w = p.Select(x => x / ps).ToArray();
			return w;
		}

		/// <summary>
		/// 计算判断矩阵A的一致性比例CR
		/// </summary>
		/// <param name="a"></param>
		/// <returns>一致性比例CR</returns>
		public static double GetConsistenceRatio(double[,] a)
		{
			Debug.Assert(a.GetLength(0) == a.GetLength(1));
			var n = a.GetLength(0);
			Debug.Assert(n >= 2);

			// 计算相对权重W
			var w = GetWeightVector(a);

			// 判断矩阵A的最大特征根
			var lmax = a.MatrixMultiply(w)
				           .VectorDotOperation(w, (x, y) => x / y)
				           .Sum() / n;

			// 一致性指标CI
			var ci = (lmax - n) / (n - 1);

			// 一致性比例
			var ris = new[]
			{
				0d, 0d, 0.58, 0.90, 1.12,
				1.24, 1.32, 1.41, 1.45, 1.49,
				1.51, 1.54, 1.56, 1.58, 1.59,
			};
			Debug.Assert(n < ris.Length);
			var ri = ris[n - 1];
			var cr = ci / ri;
			return cr;
		}

		/// <summary>
		/// 计算判断矩阵A的诱导矩阵C
		/// </summary>
		/// <param name="a"></param>
		/// <returns>诱导矩阵C</returns>
		public static double[,] GetInduceMatrix(double[,] a)
		{
			Debug.Assert(a.GetLength(0) == a.GetLength(1));
			var n = a.GetLength(0);

			var b = new double[n, n];
			for (var j = 0; j < n; j++)
			{
				var aj = 0d;
				for (var i = 0; i < n; i++)
				{
					aj += a[i, j];
				}

				for (var i = 0; i < n; i++)
				{
					b[i, j] = a[i, j] / aj;
				}
			}

			var w = new double[n];
			for (var i = 0; i < n; i++)
			{
				for (var j = 0; j < n; j++)
				{
					w[i] += b[i, j];
				}
				w[i] = w[i] / n;
			}

			var c = new double[n, n];
			for (var i = 0; i < n; i++)
			{
				for (var j = 0; j < n; j++)
				{
					c[i, j] = b[i, j] / w[i];
				}
			}

			return c;
		}

		/// <summary>
		/// 根据专家权重与专家打分计算风险事件的模糊概率FFR
		/// </summary>
		/// <param name="expertWeights"></param>
		/// <param name="scores"></param>
		/// <returns></returns>
		public static double GetFuzzyProbabilityByExpertWeights(IList<double> expertWeights, 
			IList<int> scores)
		{
			Debug.Assert(expertWeights.Count == scores.Count);
			var (a, b, c, d) = GetFuzzySetCoefficient(expertWeights, scores);
			var ffr = GetFuzzyProbability(a, b, c, d);
			return ffr;
		}

		/// <summary>
		/// 求平均模糊函数中的4个系数
		/// </summary>
		/// <param name="expertWeights">专家权重系数</param>
		/// <param name="scores">发生概率等级(1~7)</param>
		/// <returns></returns>
		public static (double a, double b, double c, double d)
			GetFuzzySetCoefficient(IList<double> expertWeights, IList<int> scores)
		{
			Debug.Assert(expertWeights.Count == scores.Count);
			Debug.Assert(Math.Abs(expertWeights.Sum() - 1d) < 1e-6);
			Debug.Assert(scores.All(s => 1 <= s && s <= ExpertFormOptions.PossibilityLevels.Length));

			var x1s = new[] {0d, 0.1, 0.2, 0.4, 0.5, 0.7, 0.8};
			var x2s = new[] {0d, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1};
			var x3s = new[] {0.2, 0.3, 0.5, 0.6, 0.8, 0.9, 1d};
			var x4s = new[] {0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0d};

			var x1 = expertWeights
				.Zip(scores, (w, s) => w * x1s[s - 1])
				.Sum();
			var x2 = expertWeights
				.Zip(scores, (w, s) => w * x2s[s - 1])
				.Sum();
			var x3 = expertWeights
				.Zip(scores, (w, s) => w * x3s[s - 1])
				.Sum();
			var x4 = expertWeights
				.Zip(scores, (w, s) => w * x4s[s - 1])
				.Sum();
			return (x1, x2, x3, x4);
		}

		/// <summary>
		/// 计算模糊发生概率FFR
		/// </summary>
		/// <param name="a"></param>
		/// <param name="b"></param>
		/// <param name="c"></param>
		/// <param name="d"></param>
		/// <returns></returns>
		public static double GetFuzzyProbability(double a, double b, double c, double d)
		{
			Debug.Assert(a < c && b <= 0.10001 && d <= 0.10001);
			var y1 = 1 - (a + b) / (1 + b);
			var y2 = 1 - (c - d) / (1 - d);
			var y3 = a / (1 - b);
			var y4 = c / (1 + d);
			var fpsl = Math.Max(y1, y2);
			var fpsr = Math.Max(y3, y4);
			var fps = (fpsr + 1d - fpsl) / 2d;

			if (Math.Abs(fps) < 1e-8)
				return 0;

			var k = Math.Pow((1 - fps) / fps, 1d / 3d) * 2.301;
			var ffr = 1d / Math.Pow(10d, k);
			return ffr;
		}

		/// <summary>
		/// 确定风险事件发生概率的等级
		/// </summary>
		/// <param name="ffr">风险事件发生概率FFR</param>
		/// <returns></returns>
		public static int GetRiskEvenPosibilityLevel(double ffr)
		{
			Debug.Assert(ffr >= 0);
			var spliters = new[] {0d, 1e-4, 1e-3, 1e-2, 1e-1, 1d};
			var levels = new[] {1, 2, 3, 4, 5};
			var level = MatchLevel(ffr, spliters, levels,
				(v, l, u) => l <= v && v < u);
			return level;
		}

		/// <summary>
		/// 确定风险事件损失的等级
		/// </summary>
		/// <param name="ffr">风险损失值FFR</param>
		/// <returns></returns>
		public static int GetRiskEvenLostLevel(double ffr)
		{
			Debug.Assert(ffr >= 0);
			var spliters = new[] {0d, 1e-4, 1e-3, 1e-2, 1e-1, 1d};
			var levels = new[] {1, 2, 3, 4, 5}; // {A, B, C, D, E}
			var level = MatchLevel(ffr, spliters, levels,
				(v, l, u) => l <= v && v < u);
			return level;
		}

		/// <summary>
		/// 确定可接受水平等级
		/// </summary>
		/// <param name="riskLevel">风险发生概率等级(1~5)</param>
		/// <param name="lostLevel">风险损失等级(1~5)</param>
		/// <returns>可接受水平等级</returns>
		public static int GetAcceptanceLevel(int riskLevel, int lostLevel)
		{
			Debug.Assert((1 <= riskLevel && riskLevel <= 5)
			             && (1 <= lostLevel && lostLevel <= 5));
			var acceptanceLevelTable = new[]
			{
				(AcceptanceLevel:1, RiskLostLevels:new[] {11, 21, 12, 13}),
				(AcceptanceLevel:2, RiskLostLevels:new[] {31, 22, 32, 23, 14, 15}),
				(AcceptanceLevel:3, RiskLostLevels:new[] {41, 51, 42, 33, 24, 25}),
				(AcceptanceLevel:4, RiskLostLevels:new[] {52, 43, 53, 34, 44, 35}),
				(AcceptanceLevel:5, RiskLostLevels:new[] {54, 45, 55}),
			};
			var acceptanceLevel = acceptanceLevelTable
				.First(al => al.RiskLostLevels.Contains(riskLevel * 10 + lostLevel))
				.AcceptanceLevel;
			return acceptanceLevel;
		}

		/// <summary>
		/// 根据分隔点确定数据的分级
		/// </summary>
		/// <typeparam name="TValue">需要分级的数据类型</typeparam>
		/// <typeparam name="TLevel">分级数据类型</typeparam>
		/// <param name="value">需要分级的数据</param>
		/// <param name="spliters">分隔点列表</param>
		/// <param name="levels">分级列表</param>
		/// <param name="predict">分级判定函数</param>
		/// <returns>分级数</returns>
		public static TLevel MatchLevel<TValue, TLevel>(
			TValue value,
			IList<TValue> spliters,
			IList<TLevel> levels,
			Func<TValue, TValue, TValue, bool> predict)
			where TValue : IComparable
		{
			Debug.Assert(spliters != null && levels != null
			             && levels.Count >= 1 && spliters.Count == levels.Count + 1);
			Debug.Assert(predict(value, spliters.First(), spliters.Last()));

			var level = levels.First();
			for (var i = 0; i < levels.Count; i++)
			{
				var lower = spliters[i];
				var upper = spliters[i + 1];
				Debug.Assert(lower.CompareTo(upper) < 0);
				if (predict(value, lower, upper))
				{
					level = levels[i];
					break;
				}
			}
			return level;
		}
	}

	#region Risk Event classes

	/// <summary>
	/// 专家信息
	/// </summary>
	public class Expert
	{
		public string Name { get; set; }

		public IList<int> ProfessionalScores { get; set; }
		public double Weight { get; set; }
	}

	/// <summary>
	/// 风险事件
	/// </summary>
	public class RiskEvent
	{
		public int Uid { get; set; }
		public string Name { get; set; }
		public string FullName => $"{Name}-U{Uid}";
		public string UidName => $"U{Uid}";
		public int Level { get; set; }

		public List<int> ImportanceScores { get; } = new List<int>();
		public double Weight { get; set; } = 1d;

		public List<int> PossibilityScores { get; } = new List<int>();
		public List<int> HumanLostScores { get; } = new List<int>();
		public List<int> EconomicLostScores { get; } = new List<int>();
		public List<int> TimeLostScores { get; } = new List<int>();
		public List<int> StructureLostScores { get; } = new List<int>();

		public double Possibility { get; set; } = -1d;
		public double TotalLost { get; set; } = -1d;

		public int PossibilityLevel { get; set; }
		public int LostLevel { get; set; }
		public int AcceptanceLevel { get; set; }

		public string FormatedWeight => $"{Weight:F3}";
		public string FormatedPossibility => $"{Possibility:F5}";
		public string FormatedTotalLost => $"{TotalLost:F5}";
		public string FormatedPossibilityLevel => $"{PossibilityLevel}";
		public string FormatedLostLevel => new[] {"A", "B", "C", "D", "E"}[LostLevel - 1];

		public string FormatedAcceptanceLevel
			=> new[] {"可忽略", "可容许", "可接受", "不可接受", "拒绝接受"}[AcceptanceLevel - 1];

		public double HumanLost { get; set; } = -1d;
		public double EconomicLost { get; set; } = -1d;
		public double TimeLost { get; set; } = -1d;
		public double StructureLost { get; set; } = -1d;

		public int Id { get; set; }
		public int ParentId { get; set; }
		public IList<RiskEvent> ChildrenRiskEvents { get; set; }
		public bool IsPrimaryEvent => ChildrenRiskEvents == null || ChildrenRiskEvents.Count <= 0;

		public int CellRow { get; set; }
		public int CellCol { get; set; }

		public bool IsSelected { get; set; } = false;
	}

	public class PotentialRiskEvent
	{
		public string Name { get; set; }
		public int Level { get; set; }

		[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
		public IList<PotentialRiskEvent> ChildRiskEvents { get; set; }
	}

	public class TotalLost
	{
		public List<int> HumanLostImportanceScores { get; } = new List<int>();
		public List<int> EconomicLostImportanceScores { get; } = new List<int>();
		public List<int> TimeLostImportanceScores { get; } = new List<int>();
		public List<int> StructureLostImportanceScores { get; } = new List<int>();

		public double HumanLostWeight { get; set; }
		public double EconomicLostWeight { get; set; }
		public double TimeLostWeight { get; set; }
		public double StructureLostWeight { get; set; }
	}

	#endregion

	#region Matrix Operations

	public static class MatrixOperation
	{
		public static double[] MatrixMultiply(this double[,] a, double[] b)
		{
			Debug.Assert(a.GetLength(0) == a.GetLength(1)
			             && a.GetLength(0) == b.Length);
			var c = new double[b.Length];
			for (var i = 0; i < b.Length; i++)
			{
				for (var j = 0; j < b.Length; j++)
				{
					c[i] += a[i, j] * b[j];
				}
			}
			return c;
		}

		public static double[] VectorDotOperation(this double[] a, double[] b,
			Func<double, double, double> f)
		{
			Debug.Assert(a.Length == b.Length);
			var c = new double[a.Length];
			for (var i = 0; i < a.Length; i++)
			{
				c[i] = f(a[i], b[i]);
			}
			return c;
		}
	}

	#endregion
}
